<?php
/*
 * Author  : top-songshijie
 * Email   : 997031758@qq.com
 * DateTime: 2023/06/14 17:45
 */

namespace SSJ\LaravelHelper\Services\Lock;

use Illuminate\Support\Facades\Redis;

class LockService
{
    protected $prefix = 'lhlock_';

    /**
     * exec
     *
     * @param string   $key
     * @param callable $fn
     * @param int      $ex
     *
     * @return mixed
     */
    public function exec($key, $fn, $ex)
    {
        $value = md5(time() . uniqid());
        try {
            $this->lock($key, $value, $ex);
            return $fn();
        } finally {
            $this->unlock($key, $value);
        }
    }

    /**
     * lock
     *
     * @param string $key
     * @param string $value
     * @param int    $ex
     *
     * @return bool
     */
    public function lock($key, $value, $ex)
    {
        if ($this->tryLock($key, $value, $ex)) {
            return true;
        }
        usleep(200);
        $this->lock($key, $value, $ex);
    }

    /**
     * unlock
     *
     * @param string $key
     * @param string $value
     *
     * @return mixed
     */
    public function unlock($key, $value)
    {
        $script = <<< EOF
if (redis.call("get", KEYS[1]) == ARGV[1]) then
	return redis.call("del", KEYS[1])
else
	return 0
end
EOF;
        return Redis::connection()->eval($script, 1, $this->prefix . $key, $value);
    }

    /**
     * tryLock
     *
     * @param string $key
     * @param string $value
     * @param int    $ex
     *
     * @return mixed
     */
    protected function tryLock($key, $value, $ex)
    {
        return Redis::connection()->set($this->prefix . $key, $value, 'EX', $ex, 'NX');
    }
}
